Skip to main content

SMART on FHIR

SMART on FHIR answers a question base FHIR leaves open: how does a third-party application get permission to read this patient's record, and how does it know which patient the clinician is looking at?

It is a profile of OAuth 2.0 and OpenID Connect plus a set of conventions for launch context and health-specific scopes. It is the reason an app can be written once and run inside more than one EMR.


The problem it solves​

Without SMART, every EMR invents its own app integration: its own token format, its own way of saying "the clinician is currently viewing patient 12345", its own permission vocabulary. An app vendor then rebuilds integration per EMR, and a ministry cannot commission one decision-support app for a whole country.

SMART standardises three things:

  1. Authorisation — how the app obtains an access token
  2. Launch context — how the app learns the current patient, encounter and user
  3. Scopes — a vocabulary for what the app is asking to do

Launch modes​

EHR launch​

The clinician is inside the EMR and opens the app from it. The EMR knows the context and passes it along.

Clinician clicks "Growth chart" inside the EMR
│
▼
EMR redirects to app with ?iss=<fhir base>&launch=<opaque token>
│
▼
App fetches <iss>/.well-known/smart-configuration ← discovery
│
▼
App → authorization endpoint
(client_id, scope, redirect_uri, launch, state, PKCE challenge, aud)
│
▼
User authenticates / authorisation server applies policy
│
▼
Redirect back with authorization code
│
▼
App → token endpoint (code + PKCE verifier)
│
▼
Token response: access_token, scope, patient, encounter, id_token, expires_in
│
▼
App calls FHIR API with Bearer token, already knowing the patient

The launch parameter is opaque to the app — it is a handle the authorisation server exchanges for context. The context comes back in the token response, not in the URL.

Standalone launch​

The user starts in the app (a patient portal, a research app) and chooses which server to connect to. Same flow without the launch parameter; the authorisation server establishes the patient context, usually because the user is the patient.

Backend services (system-to-system)​

No user is present — a nightly quality-measure export, a registry sync. Uses the OAuth client credentials grant with an asymmetric JWT client assertion (the client signs with a private key; the server holds the public key via a JWKS URL). No shared secrets, no refresh tokens.

This is the flow that pairs with Bulk Data.


Scopes​

SMART scopes are structured, not free text:

patient/Observation.rs read + search Observations for the in-context patient
user/Patient.r read any Patient the authenticated user may see
system/Immunization.rs backend access, no user context
patient/*.rs everything for this patient — grant with care
openid fhirUser identify the end user
launch request launch context
launch/patient standalone launch, ask which patient
offline_access issue a refresh token for use when the user is absent
online_access refresh only while the session lasts

Version note. SMART v1 used .read / .write. SMART v2 replaced these with granular CRUDS letters — c, r, u, d, s — so Observation.rs means read and search but not create. Many servers accept both for compatibility. State which version your implementation guide requires; do not leave it to inference.

SMART v2 also adds fine-grained scopes with search parameters, e.g. patient/Observation.rs?category=laboratory — an app can be granted lab results without being granted mental health notes. This is the mechanism most useful for real-world consent, and support for it varies by server.

Scopes are not the whole authorisation model​

A scope says what the app asked for. It does not say whether this user, in this care relationship, for this purpose, may see this patient's data. That decision belongs to the authorisation server and the consent service. An architecture that treats a granted scope as sufficient authorisation has no access control.


Discovery​

Every SMART-enabled FHIR server publishes:

GET [fhir base]/.well-known/smart-configuration

returning the authorization and token endpoints, supported scopes, supported capabilities (launch-ehr, client-public, permission-v2, …) and the JWKS URL. Clients must read this rather than hard-coding endpoints.


CDS Hooks​

A companion specification, and often confused with SMART. CDS Hooks lets the EMR call out at defined points in the workflow — patient-view, order-select, order-sign, appointment-book — to an external decision service, which returns cards: information, suggestions, or a link to launch a SMART app.

Clinician opens the chart
│ POST /cds-services/anc-danger-signs
▼ { hook, context, prefetch: { patient, observations } }
┌─────────────────────┐
│ Decision service │ evaluates guideline logic (often CQL)
└─────────┬───────────┘
│ { cards: [ { summary, indicator, suggestions, links } ] }
▼
EMR renders a card; clinician may accept a suggestion or launch a SMART app

The pairing — CDS Hooks to surface the recommendation, SMART to open a full app when the clinician wants detail — is the standard shape for computable guidelines.


FHIRcast​

A third member of the family, for context synchronisation: keeping several open applications on the same patient and study as the user navigates. Common in radiology, where a PACS viewer, a reporting system and an AI tool must follow each other.


Implementation checklist​

For a server:

  • Publish .well-known/smart-configuration and a CapabilityStatement
  • Support PKCE; require it for public clients
  • Validate aud on every authorisation request
  • Enforce scopes on every FHIR interaction — not only at the token endpoint
  • Apply consent and care-relationship policy in addition to scopes
  • Support refresh tokens with offline_access, and support revocation
  • Log every access as AuditEvent with the app, user and patient
  • Provide a sandbox with synthetic data, and a registration process

For an app:

  • Never embed a client secret in a browser or mobile app; use PKCE
  • Request the narrowest scopes that work, and explain them to the user
  • Handle scope downgrade — the server may grant less than you asked
  • Treat tokens as short-lived; refresh, do not re-launch
  • Fail safely and legibly when context is missing

Where it fits architecturally​

SMART app ──┐
│ OAuth 2.0 / OIDC
▼
Authorisation server ◀── consent service, care-relationship service
│
│ access token (scopes + patient context)
▼
FHIR server / API gateway ──▶ audit log
│
▼
Shared health record / EMR

Note that the authorisation server is a distinct component from the FHIR server. Combining them is common in single-vendor deployments and a mistake in national architectures, where identity is a shared service — see identity and security.


References​